#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <float.h>

#include "pub_lib.h"

struct rasterize_info {
	//screen info
	int width;
	int height;

	/* Color.a : if set 255, sampled point, should draw it
	 * default Color.a is 0, no need care */
	Color* frame_buf;// rgba
	float* depth_buf; //z-buf
};
#define sampled_point(color) (color.a)

/*
 * Vertex must be provided in counter-clockwise order
	when call function DrawTriangle
 * */
struct triangle {
	Quaternion v0, v1, v2; //init position of triangle apex
	Vector3 col0, col1, col2; //save color information
	int clockwise; //1 if clockwise

	Vector3 vn0, vn1, vn2; //used to z-buffer algorithm
	Vector2 n0, n1, n2;//used to draw triangle, same as vn0, vn1, vn2
};

Quaternion z_axis = { 0.0, 0.0, 1.0, 0.0};
const int screenWidth = 700;
const int screenHeight = 700;

/* init triangle with apex position */
void init_triangle(struct triangle *ptri, Quaternion v0, Quaternion v1, Quaternion v2)
{
	ptri->clockwise = is_clockwiseQ(v0, v1, v2);
	if ( ptri->clockwise){
		//v0 - v1 - v2
		ptri->v0 = v0;
		ptri->v1 = v1;
		ptri->v2 = v2;
	} else {
		//v0 - v2 - v1
		ptri->v0 = v0;
		ptri->v1 = v2;
		ptri->v2 = v1;
	}
}

/* init triangle with apex color*/
void init_triangle_color(struct triangle *ptri, Vector3 v0c, Vector3 v1c, Vector3 v2c)
{
	if ( ptri->clockwise){
		ptri->col0 = v0c;
		ptri->col1 = v1c;
		ptri->col2 = v2c;
	} else {
		ptri->col0 = v0c;
		ptri->col1 = v2c;
		ptri->col2 = v1c;
	}
}

/*
 *  1, 0, 0, -eye_pos[0],
    0, 1, 0, -eye_pos[1],
    0, 0, 1, -eye_pos[2],
    0, 0, 0, 1;

 * */
Matrix get_view_matrix(Quaternion eye_pos)
{
	Matrix result = { 1.0f, 0.0f, 0.0f, -eye_pos.x,
		0.0f, 1.0f, 0.0f, -eye_pos.y,
		0.0f, 0.0f, 1.0f, -eye_pos.z,
		0.0f, 0.0f, 0.0f, 1.0f };

	return result;
}

/*
  cos(radian), -sin(radian), 0, 0,
  sin(radian), cos(radian), 0, 0,
            0, 0, 1, 0,
            0, 0, 0, 1;

MatrixRotate((Vector3){ 0.0f, 0.0f, 1.0f }, rotation_angle * DEG2RAD);
Rotation around z only
 * */
Matrix get_model_matrix(float rotation_angle)
{
	float cosv, sinv;
	float angle;

	angle = rotation_angle * DEG2RAD;
	cosv = cosf(angle);
	sinv = sinf(angle);

	Matrix result = {
		cosv, -sinv, 0.0f, 0.0f,
		sinv, cosv, 0.0f, 0.0f,
		0.0f, 0.0f, 1.0f, 0.0f,
		0.0f, 0.0f, 0.0f, 1.0f };

	return result;
}

/*
 * MatrixPerspective(eye_fov * DEG2RAD, aspect_ratio, zNear, zFar);
 ensure zNear and zFar is negative value
 * */
Matrix get_projection_matrix(float eye_fov, float aspect_ratio,
                        float zNear, float zFar)
{
	float val, top, bottom, left, right;
	float rl, tb, fn;
	Matrix result = {0};

	val = tan(eye_fov * 0.5 * DEG2RAD);
	top = fabsf(zNear) * val;
	bottom = -top;
	right = aspect_ratio * top;
	left = -right;

	rl = right - left;
    tb = top - bottom;
    fn = zFar - zNear;

	result.m0 = zNear / right;
    result.m1 = 0.0f;
    result.m2 = 0.0f;
    result.m3 = 0.0f;

    result.m4 = 0.0f;
    result.m5 = zNear / top;
    result.m6 = 0.0f;
    result.m7 = 0.0f;

    result.m8 = 0.0f;
    result.m9 = 0.0f;
    result.m10 = -(zFar + zNear)/fn;
    result.m11 = -1.0f;

    result.m12 = 0.0f;
    result.m13 = 0.0f;
    result.m14 = -(zFar*zNear*2.0f)/fn;
    result.m15 = 0.0f;

	return result;
}


/* viewport transformation
 * save final coordinates to struct triangle
 * */
void viewport_trans(Quaternion point, Vector3* new, Vector2* vector, int width, int height)
{
	Quaternion v;
	float w;

	print_qm("homogenous", point);
	//homogenous coordinates to orthonormal coordinates
	w = point.w;
	v.x = point.x / w;
	v.y = point.y / w;
	v.z = point.z / w;
	v.w = 1.0f;
	print_qm("orthonormal", v);

	//viewport transformation
	point.x = 0.5 * width * (1.0 - v.x);
	point.y = 0.5 * height * (v.y + 1.0);

	//save new coordinates
	new->x = point.x;
	new->y = point.y;
	new->z = w;

	vector->x = new->x;
	vector->y = new->y;
}

/*
 * according to rotation angle, update the MVP matrix
 * and triangle apex
 * */
void update_triangle_mvp(struct triangle* ptri, float angle, Quaternion eye_pos)
{
	Matrix model, view, projection;
	Matrix pv, mvp;
	Quaternion newV0, newV1, newV2;

	//calc transformation M, V, P
	model = get_model_matrix(angle);
	view = get_view_matrix(eye_pos);
	projection = get_projection_matrix(45, 1, -0.1, -50);

	//calc MVP: projection - view - Module
	pv = matrxiMul(projection, view); //pv = P * V
	mvp = matrxiMul(pv, model); //MVP = PV * M = P * V * M

	//transform points
	newV0 = QuaternionTransform(ptri->v0, mvp);
	newV1 = QuaternionTransform(ptri->v1, mvp);
	newV2 = QuaternionTransform(ptri->v2, mvp);

	//Viewport transformation
	viewport_trans(newV0, &ptri->vn0, &ptri->n0, screenWidth, screenHeight);
	viewport_trans(newV1, &ptri->vn1, &ptri->n1, screenWidth, screenHeight);
	viewport_trans(newV2, &ptri->vn2, &ptri->n2, screenWidth, screenHeight);

#ifdef DEBUG_VER
	print_mat("model", model);
	print_mat("view", view);
	print_mat("projection", projection);
	print_mat("mvp", mvp);
#endif
}

struct triangle tri1;
struct triangle tri2;

struct rasterize_info raster_info;

Color color_tri1 = {217, 238, 185, 255};
Color color_tri2 = {185, 217, 238, 255};

/* initialize the rasterization */
void rasterize_init(struct rasterize_info *raster, int width, int height)
{
	raster->width = width;
	raster->height = height;
	int count;

	count = width * height;
	raster->frame_buf = calloc(count, sizeof(Color));
	raster->depth_buf = calloc(count, sizeof(float));

	int i;
	for( i = 0; i < count; i++)
		raster->depth_buf[i] = FLT_MAX;
}

void rasterize_exit(struct rasterize_info *raster)
{
	free(raster->depth_buf);
	free(raster->frame_buf);
}

/*
 * if point p(x, y) inside triangle(a, b, c)
 * */
bool insideTriangle(float x, float y, Vector3 a, Vector3 b, Vector3 c, Vector3* point)
{
	Vector3 p = {x, y, 1.0f};

	Vector3 AB = Vector3Subtract(b, a); //B - A;
    Vector3 BC = Vector3Subtract(c, b); //C - B;
    Vector3 CA = Vector3Subtract(a, c); //A - C;

    Vector3 AP = Vector3Subtract(p, a); //P - A;
    Vector3 BP = Vector3Subtract(p, b); //P - B;
    Vector3 CP = Vector3Subtract(p, c); //P - C;

	Vector3 result = Vector3CrossProduct(AB, AP);
	float z1 = result.z;

	result = Vector3CrossProduct(BC, BP);
	float z2 = result.z;

	result = Vector3CrossProduct(CA, CP);
	float z3 = result.z;

	*point = p;

    return (z1 > 0 && z2 > 0 && z3 > 0) || (z1 < 0 && z2 < 0 && z3 < 0);
}

/* interv at post-projection
 * now convert to pre-projection
 * return z value after interpolated */
float convert_to_original(struct triangle *ptri, Vector3 interv)
{
	float z;
	// u/Za + v/Zb + w/Zc, (Zz, Zb, Zc) is depth value
	z = interv.x/ptri->v0.z + interv.y/ptri->v1.z + interv.z/ptri->v2.z;
	z = fabs(1.0 / z);
	return z;
}

/*
 * update the rasterization buffer by point p in triangle(a, b, c)
 * please ensure p in triangle plane
 * */
void zbuf_update(struct triangle *ptri, struct rasterize_info *raster,
	Vector3 p, int x, int y, Vector3 a, Vector3 b, Vector3 c)
{
	Vector3 interV;
	int index;
	float z_interpolated;

	/* If so, use the following code to get the interpolated z value
	 * Compute barycenter coordinates (u, v, w) for point p
	 * */
	interV = Vector3Barycenter(p, a, b, c);
	//interV = barycenter(p, a, b, c);
	z_interpolated = convert_to_original(ptri, interV);
	index = get_point_index(x, y, raster->width, raster->height);
	if (z_interpolated >= raster->depth_buf[index]) {
		return;
	}

	raster->depth_buf[index] = z_interpolated;
	raster->frame_buf[index] = makeRGBA(ptri->col0); //Only one color per triangle
	//DrawPixel(x, y, raster->frame_buf[index]);
}

//draw the triangle
void rasterize_triangle(struct triangle *ptri, struct rasterize_info *raster)
{
	Vector3 a = {ptri->vn0.x, ptri->vn0.y, ptri->vn0.z};
	Vector3 b = {ptri->vn1.x, ptri->vn1.y, ptri->vn1.z};
	Vector3 c = {ptri->vn2.x, ptri->vn2.y, ptri->vn2.z};

	BoundingBox box;
	int x, y;
	Vector3 point;

	//Find out the bounding box of current triangle
	box = calcBoundingBox(a, b, c);
	// iterate through the pixel and find if the current pixel is inside the triangle
    for (x = (int)(box.min.x); x <= (int)(box.max.x); x++)
    {
        for (y = (int)(box.min.y); y <= (int)(box.max.y); y++)
        {
			if (!insideTriangle(x+0.5f, y+0.5f, a, b, c, &point)) {
				continue;
            }
			zbuf_update(ptri, raster, point, x, y, a, b, c);
		}
	}
}

/* scan the whole framebuf */
void draw_triangle(struct rasterize_info *raster)
{
	int i, j, index;

	for(i = 0; i < raster->width; i++) {
		for ( j = 0; j < raster->height; j++) {
			index = get_point_index(i, j, raster->width, raster->height);
			if (sampled_point(raster->frame_buf[index])) {
				DrawPixel(i, j, raster->frame_buf[index]);
			}
		}
	}
}

int main(int argc, char** argv)
{
	// Initialization
	char key;
	float angle = 0.0;

	//apex of triangle1
	Quaternion v0 = {2.0, 0.0, -2.0, 1};
	Quaternion v1 = {0.0, 2.0, -2.0, 1};
	Quaternion v2 = {-2.0, 0.0, -2.0, 1};
	//apex of triangle2
	Quaternion v10 = {3.5, -1, -5, 1};
	Quaternion v11 = {2.5, 1.5, -5, 1};
    Quaternion v12 = {-1, 0.5, -5, 1};

	//colors of triangle1
	Vector3	v0c = {217.0, 238.0, 185.0};
	Vector3 v1c = {217.0, 238.0, 185.0};
	Vector3	v2c = {217.0, 238.0, 185.0};
	//colors of triangle2
	Vector3 v10c = {185.0, 217.0, 238.0};
	Vector3 v11c = {185.0, 217.0, 238.0};
	Vector3	v12c = {185.0, 217.0, 238.0};

	Quaternion eye_pos = {0, 0, 5, 1};
	//--------------------------------------------------------------------------------------
	init_triangle(&tri1, v0, v1, v2); //triangle apex position
	init_triangle_color(&tri1, v0c, v1c, v2c); //triangle apex color

	init_triangle(&tri2, v10, v11, v12);
	init_triangle_color(&tri2, v10c, v11c, v12c);

	rasterize_init(&raster_info, screenWidth, screenHeight);

	update_triangle_mvp(&tri1, angle, eye_pos);
	update_triangle_mvp(&tri2, angle, eye_pos);

	rasterize_triangle(&tri1, &raster_info);
	rasterize_triangle(&tri2, &raster_info);

	InitWindow(screenWidth, screenHeight, "homework2 - fill two triangles");
	SetTargetFPS(60);               // Set our game to run at 60 frames-per-second
	//--------------------------------------------------------------------------------------

	// Main game loop
	while (!WindowShouldClose())    // Detect window close button or ESC key
	{
		// Update
		//----------------------------------------------------------------------------------
		// TODO: Update your variables here
		key = (char)GetKeyPressed();
		if (key == ESC_KEY) {
			break;
		}

		//----------------------------------------------------------------------------------

		// Draw
		//----------------------------------------------------------------------------------
		BeginDrawing();
		ClearBackground(BLACK);

		draw_triangle(&raster_info);

		EndDrawing();
		//----------------------------------------------------------------------------------
	}

	// De-Initialization
	//--------------------------------------------------------------------------------------
	CloseWindow();        // Close window and OpenGL context
	//--------------------------------------------------------------------------------------

	rasterize_exit(&raster_info);
	return 0;
}
